import java.io.*;
import java.net.*;
import java.util.concurrent.Semaphore;

public class TokenPassing extends Thread {
    private int port;
    Semaphore token;
    private DatagramSocket socket = null;
    private PeerInfo predecessor;
    private PeerInfo successor;
    private boolean activeFlag;
    InetAddress addr;

    private void bindPort() {
	port = Utils.Random(1024, 65535);
	try {
	    socket = new DatagramSocket(port);

	} catch (SocketException e) {
	    e.printStackTrace();
	    bindPort();
	}
    }

    public TokenPassing(Semaphore token) throws IOException {

	this.token = token;
	this.activeFlag = false;// not active
	socket = new DatagramSocket();
	// bindPort();
	port = socket.getLocalPort();
	String laddr = SystemConfig.getAddr();
	// System.out.println("udp server port is "+port);
	Utils.writeLog("port is " + port);
	this.predecessor = new PeerInfo(SystemConfig.getPid(), laddr, port);
	this.successor = new PeerInfo(SystemConfig.getPid(), laddr, port);
	this.setName("tokenpassing");
	try {
	    token.acquire();// there is no token right now
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
    }

    void initial() throws SocketException// initialization phase, send a ip,
    /*
     * 
     * */
    {
	// the datagrampacket consist of join, pid,
	// ip address and port will be automatically added by UDP
	String data = new String();
	data += "JOIN ";
	data += SystemConfig.getPid();

	try {
	    byte buf[] = data.getBytes("US-ASCII");
	    InetAddress group = InetAddress.getByName(SystemConfig
		    .getGroupAddr());
	    int groupPort = SystemConfig.getGroupPort();
	    DatagramPacket packet = new DatagramPacket(buf, buf.length, group,
		    groupPort);
	    socket.send(packet);
	    Utils.writeLog("initial called");

	} catch (UnsupportedEncodingException e) {
	    e.printStackTrace();
	} catch (IOException e) {
	    e.printStackTrace();
	}

	DatagramPacket packet;
	byte[] buf = new byte[256];
	packet = new DatagramPacket(buf, buf.length);
	socket.setSoTimeout(5000);
	try {
	    socket.receive(packet);
	    joinRing(packet);
	} catch (IOException e) {
	    e.printStackTrace();
	}

    }

    void handlePacket(DatagramPacket dpacket) 
    /*
     * TODO there are 2 kinds of packet we need to handle: token and pred
     * token contains the token, pred asks change successor
     * */
    {
	String received;
	try {
	    received = new String(dpacket.getData(), 0, dpacket.getLength(),
		    "US-ASCII");
	    // System.out.println("Quote of the Moment: " + received);
	    if(received.toUpperCase().contains("JOIN"))
	    {    Utils.writeLog(received);
	    token.release();
	    sleep(20);// give the main thread a chance to get token
	    passToken();
	    }
	    else if(received.toUpperCase().contains("PRED"))//TODO we have 
	    {
		String addr=dpacket.getAddress().getHostAddress();
		int port =dpacket.getPort();
		this.successor.setAddr(addr);
		this.successor.setPort(port);
	    }
	    // activeFlag = true;
	} catch (UnsupportedEncodingException e) {
	    e.printStackTrace();
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}

    }

    void passToken() {
	synchronized (token) {
	    if (token.availablePermits() > 0)// main thread did not need the
	    // token
	    {
		try {
		    token.acquire();
		    sleep(100);
		    Utils.writeLog(0);

		    // the token is not released here, however,
		    // token will be released when received the token packet
		} catch (InterruptedException e) {
		    // TODO Auto-generated catch block
		    e.printStackTrace();
		}

	    } else // main thread is using the token, log should be different
	    // and writen by main
	    {

		try {
		    token.acquire();
		    sleep(100);
		} catch (InterruptedException e) {
		    // TODO Auto-generated catch block
		    e.printStackTrace();
		}
	    }
	    InetAddress peerAddress;
	    try {
		peerAddress = InetAddress.getByName(successor.getAddr());

		int port = successor.getPort();
		String data = "Token";
		byte buf[] = data.getBytes("US-ASCII");
		DatagramPacket tpacket = new DatagramPacket(buf, buf.length,
			peerAddress, port);
		sendPacket(tpacket);
	    } catch (UnknownHostException e) {
		e.printStackTrace();
	    } catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	    }
	}
    }

    void joinRing(DatagramPacket rpacket) {
	try {
	    socket.setSoTimeout(0);//erase the timeout set by 5 seconds, 
	    
	} catch (SocketException e1) {
	   
	    e1.printStackTrace();
	}
	String received;
	try {
	    received = new String(rpacket.getData(), 0, rpacket.getLength(),
		    "US-ASCII");
	    String pre[] = received.split(" ");
	    this.successor.addr = rpacket.getAddress().getHostAddress();
	    this.successor.port = rpacket.getPort();
	    this.successor.pid = pre[0];
	    this.predecessor.addr = pre[1];
	    this.predecessor.port = Integer.parseInt(pre[2]);
	    this.predecessor.pid = pre[3];
	    Utils.writeLog(received);
	    activeFlag = true;
	    
	    /*
	     * now we need to send a packet the the predecessor, 
	     * asking it to change its successor to this!
	     * */
	  
	    String data="Pred"; 
	    byte buf[]=data.getBytes("US-ASCII");
	    DatagramPacket ppacket=new DatagramPacket(buf,buf.length,
		    InetAddress.getByName(this.predecessor.addr),this.predecessor.port);
	    	sendPacket(ppacket);
	} catch (UnsupportedEncodingException e) {
	    e.printStackTrace();
	} catch (UnknownHostException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}

    }

    public boolean isActiveFlag() {
	return activeFlag;
    }

    public void setActiveFlag(boolean activeFlag) {
	this.activeFlag = activeFlag;
    }

    void sendPacket(DatagramPacket spacket) {
	try {
	    socket.send(spacket);
	    Utils.writeLog("packet send");
	} catch (IOException e) {
	    e.printStackTrace();
	}
    }

    @Override
    public void run() {
	try {
	    socket.setSoTimeout(1000);
	} catch (SocketException e1) {
	    // TODO Auto-generated catch block
	    e1.printStackTrace();
	}
	while (true) {

	    DatagramPacket packet;
	    byte[] buf = new byte[256];
	    packet = new DatagramPacket(buf, buf.length);
	    try {
		if (!activeFlag)// we did not join yet, join!
		{
		    continue;
		} else {

		    socket.receive(packet);
		    handlePacket(packet);

		}

	    } catch (IOException e) {
		e.printStackTrace();
	    }

	    // System.out.println("we are here");
	}

    }

    public PeerInfo getPredecessor() {
	return predecessor;
    }

    public PeerInfo getSuccessor() {
	return successor;
    }

}
